package dungeon;

import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;

import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Raw;

import dungeonElements.Square;
import dungeonTools.Constraint;
import dungeonTools.Coordinate;
import dungeonTools.KDTree;

/**
 * A class of dungeons that enclose other dungeons. By definition, this composite dungeon
 * will then serve as the 'parent' of its enclosed 'child' dungeon. No squares can be added
 * directly to space inside this composite dungeon, but must instead be placed at a location
 * where one of its child dungeons recedes.
 * 
 * Child dungeons must also be of a type that extends the type of this composite dungeon itself.
 * 
 * @author Tom Jacobs & Ewoud De Moor
 * @version 3.0
 *
 * @param <E> The type of square this composite dungeon accepts.
 */
public class CompositeDungeon<E extends Square> extends Dungeon<E> {
	
	/**
	 * The variable registering the amount of modifications done to this CompositDungeon
	 */
	private long modifCount;
	
	/**
	 * Creates a new composite dungeon with following sizes.  These sizes must be positive.
	 * 
	 * @param xSize
	 * 			The X-size of this new composite dungeon
	 * @param ySize
	 * 			The Y-size of this new composite dungeon
	 * @param zSize
	 * 			The Z-size of this new composite dungeon
	 * @post 	The new composite dungeon will have said sizes
	 * 			|new.getXSize == xSize;
	 * 			|new.getYSize == ySize;
	 * 			|new.getZSize == zSize;
	 * @throws IllegalArgumentException
	 * 			The sizes must both be positive.
	 * 			|(xSize < 1) || (ySize < 1) || (zSize < 1)
	 */
	@Raw
	public CompositeDungeon(long xSize, long ySize, long zSize) throws IllegalArgumentException {
		if((xSize < 1) || (ySize < 1) || (zSize < 1))
			throw new IllegalArgumentException("Sizes must be positive");
		setXSize(xSize);
		setYSize(ySize);
		setZSize(zSize);
		dungeonTree = new KDTree(Coordinate.ORIGIN, new Coordinate(getXSize()-1,getYSize()-1,getZSize()-1));
	}
		
	/**
	 * Variable registering all child dungeons of this dungeon
	 */
	private HashMap<Dungeon<? extends E>,Coordinate> childrenDungeons = new HashMap<Dungeon<? extends E>,Coordinate>();
	
	/**
	 * Returns whether a specific dungeon is a child of this dungeon
	 * 
	 * @param dungeon
	 * 			The dungeon for which will be checked
	 * @return
	 * 			Whether or not that dungeon is a child of this dungeon
	 */
	public boolean hasAsChildDungeon(Dungeon<? extends Square> dungeon) {
		return childrenDungeons.containsKey(dungeon);
	}
	
	/**
	 * Adds a dungeon as child of this composite dungeon. The dungeon will be added with its
	 * ORIGIN at the spatial coordinate given as parameter, in the composite dungeon-space.
	 * 
	 * The dungeon will have to be of a type that extends the type of this Composite Dungeon
	 * 
	 * @param dungeon
	 * 			The dungeon to add
	 * @param coordinate
	 * 			The coordinate at which the ORIGIN of said dungeon will be located
	 * @post	The dungeon will be a child of this one
	 * 			|new.getChildDungeons().containsKey(dungeon)
	 * @post	The coordinate of said dungeon will be the coordinate
	 * 			|new.getChildDungeons().get(dungeon).equals(coordinate)
	 * @throws IllegalArgumentException
	 * 			The dungeon must be addable to this dungeon at that coordinate
	 * 			!canHaveAsChildDungeonAt(dungeon, coordinate) 			
	 */
	public void addDungeon(Dungeon<? extends E> dungeon, Coordinate coordinate) throws IllegalArgumentException {
		if(!canHaveAsChildDungeonAt(dungeon, coordinate))
			throw new IllegalArgumentException("Can't have that dungeon at that location");
		modifCount++;
		dungeonTree.addDungeonToNode(dungeon, coordinate);
		childrenDungeons.put(dungeon,coordinate);
		dungeon.setParentDungeon((CompositeDungeon<? extends Square>) this);
	}
	
	/**
	 * Checks whether or not that dungeon can be added a child dungeon of this one.
	 * A KDTree is used to ensure O(log n) checking.
	 *  
	 * @param dungeon
	 * 			The dungeon for which to check
	 * @param coordinate
	 * 			The coordinate at which must be checked
	 * @return	False if one of the parameters is not initialized
	 * 			|if((dungeon == null) || (coordinate == null))
	 * 			|	then result == false
	 * 			False if that dungeon already has a parent dungeon
	 * 			|if(dungeon.hasParentDungeon())
	 * 			|	then result == false
	 *			False if placing that dungeon at that coordinate will result in a
	 *			spatial overlapping with child dungeons that have already been placed
	 *			
	 *			True in all other cases
	 *			|result == true
	 */
	public boolean canHaveAsChildDungeonAt(Dungeon<? extends E> dungeon, Coordinate coordinate){
		if((dungeon == null) || (coordinate == null))
			return false;
		if(dungeon.hasParentDungeon())
			return false;
		if(dungeonChildOverlap(dungeon,coordinate))
			return false;
		return true;
	}

	/**
	 * Checks for overlap between a new dungeon to be added and already existing childs
	 * A KDTree is used to ensure O(log n) checking.
	 * 
	 * @param dungeon
	 * 			The dungeon to check for
	 * @param coordinate
	 * 			The coordinate at which it will be added
	 * @return	Whether or not adding this dungeon would cause a spatial overlap.
	 */
	private boolean dungeonChildOverlap(Dungeon<? extends E> dungeon, Coordinate coordinate) {
		return !dungeonTree.canAddDungeonToNode(dungeon, coordinate);
	}
	
	/**
	 * Returns the child dungeon of this composite dungeon that spatially contains
	 * the coordinate, should it exist.
	 * Uses a KDTree to ensure doing so in O(log n) complexity
	 * 
	 * @param coordinate
	 * 			The coordinate for which to get the child dungeon
	 * @return	The child dungeon containing said coordinate
	 * @throws IllegalArgumentException
	 * 			Said child dungeon must exist
	 */
	@SuppressWarnings("unchecked")
	public Dungeon<? extends E> getChildDungeonContainingCoordinate(Coordinate coordinate) throws IllegalArgumentException{
		return (Dungeon<? extends E>) dungeonTree.getChildDungeonContainingCoordinate(coordinate);
	}
	
	/**
	 * Checks whether or not this composite dungeon has a child dungeon that
	 * contains a specific coordinate
	 * 
	 * @param coordinate
	 * 			The coordinate for which to check
	 * @return	Whether or not this composite dungeon contains such a child
	 */
	public boolean hasChildDungeonContainingCoordinate(Coordinate coordinate){
		return dungeonTree.hasChildDungeonContainingCoordinate(coordinate);
	}
	
	/**
	 * The private KDTree-based-datastructure used to organize child dungeons of this
	 * composite dungeon
	 */
	private KDTree dungeonTree;

	/**
	 * Removes that dungeon from this composite dungeon, if it's a child of this.
	 * 
	 * @param dungeon
	 * 			The dungeon to remove
	 * @throws IllegalArgumentException
	 * 			That dungeon must be a child of this dungeon
	 * 			|!hasAsChildDungeon(dungeon)
	 */
	public void removeChildDungeon(Dungeon<? extends E> dungeon) throws IllegalArgumentException{
		if(!hasAsChildDungeon(dungeon))
			throw new IllegalArgumentException("That dungeon isn't a child of this one.");
		modifCount++;
		dungeonTree.removeDungeonFromSubNode(dungeon, childrenDungeons.get(dungeon));
		childrenDungeons.remove(dungeon);
	}
	
	/**
	 * Returns all child dungeons of this composite dungeon, mapped on the
	 * coordinates of their origins.
	 * 
	 * @return said Map
	 */
	@Basic
	public HashMap<Dungeon<? extends E>,Coordinate> getChildDungeons(){
		return new HashMap<Dungeon<? extends E>,Coordinate>(childrenDungeons);
	}
	
	/**
	 * Returns a map of all Levels and Shafts that are children of this dungeon.
	 * They will be mapped upon the coordinate of the locations of their origin
	 * relative to this dungeons axis'.
	 * 
	 * @return said Map
	 */
	@SuppressWarnings("unchecked")
	public HashMap<Dungeon<? extends E>, Coordinate> getAllLevelsAndShafts(){
		HashMap<Dungeon<? extends E>,Coordinate> returnMap = new HashMap<Dungeon<? extends E>,Coordinate>();
		for(Dungeon<? extends E> dungeon : childrenDungeons.keySet()){
			if((dungeon instanceof Level) || (dungeon instanceof Shaft))
				returnMap.put(dungeon, childrenDungeons.get(dungeon));
			else{
				HashMap<?, Coordinate> map = (HashMap<?, Coordinate>) ((CompositeDungeon<? extends E>) dungeon).getAllLevelsAndShafts();
				for(Dungeon<? extends E> childDungeon : ((HashMap<Dungeon<? extends E>,Coordinate>)map).keySet())
					returnMap.put(childDungeon, (calculateCoordinateInThisDungeon(returnMap.get(childDungeon),childDungeon)));
				returnMap.putAll((HashMap<? extends Dungeon<? extends E>,Coordinate>) map);
			}
		}			
		return returnMap;
	}
	
	/**
	 * Calculates the relative coordinate in a child dungeon that that specific coordinate
	 * spatially situates there.
	 * 
	 * @param coordinate
	 * 			The coordinate to calculate for
	 * @param childDungeon
	 * 			The child dungeon in which said coordinate will be taken
	 * @return	The new coordinate in that child dungeon
	 * @throws IllegalArgumentException
	 * 			The parameters must be initialized
	 * 			|(coordinate == null) || (childDungeon == null)
	 * 			The coordinate must fall inside a child dungeon of this dungeon
	 * 			|!hasChildDungeonContainingCoordinate(coordinate)
	 */
	public Coordinate calculateCoordinateInChildDungeon(Coordinate coordinate, Dungeon<? extends E> childDungeon) throws IllegalArgumentException{
		if((coordinate == null) || (childDungeon == null))
			throw new IllegalArgumentException("Please initialize all parameters");
		if(!hasChildDungeonContainingCoordinate(coordinate))
			throw new IllegalArgumentException("That coordinate isn't inside any child dungeons");
		Coordinate dungeonBasisCoordinate = childrenDungeons.get(getChildDungeonContainingCoordinate(coordinate));
		return new Coordinate(coordinate.getxCoordinate() - dungeonBasisCoordinate.getxCoordinate(),
				coordinate.getyCoordinate() - dungeonBasisCoordinate.getyCoordinate(),
				coordinate.getzCoordinate() - dungeonBasisCoordinate.getzCoordinate());
	}
	
	/**
	 * Calculates a coordinate in a child dungeon, as it falls relatively in this one
	 * 
	 * @param coordinate
	 * 			The coordinate for which to calculate
	 * @param dungeon
	 * 			The child dungeon in which that coordinate will be taken
	 * @return	The new coordinate in this composite dungeons axis'.
	 * @throws IllegalArgumentException
	 * 			The parameters must be initialized
	 * 			|(coordinate == null) || (childDungeon == null)
	 * 			The coordinate must fall inside a child dungeon of this dungeon
	 * 			|!hasChildDungeonContainingCoordinate(coordinate)
	 */
	public Coordinate calculateCoordinateInThisDungeon(Coordinate coordinate, Dungeon<?> dungeon) throws IllegalArgumentException{
		if((coordinate == null) || (dungeon == null))
			throw new IllegalArgumentException("Please initialize all parameters");
		if(!hasAsChildDungeon(dungeon))
			throw new IllegalArgumentException("That dungeon isn't a child of this one");
		Coordinate dungeonBasisCoordinate = getChildDungeons().get(dungeon);
		return new Coordinate(coordinate.getxCoordinate() + dungeonBasisCoordinate.getxCoordinate(),
								coordinate.getyCoordinate() + dungeonBasisCoordinate.getyCoordinate(),
								coordinate.getzCoordinate() + dungeonBasisCoordinate.getzCoordinate());
	}
	
	/**
	 * Checks if a certain square can be placed at a certain coordinate.
	 * This method calls itself on the child dungeon the coordinate falls in.
	 * This happens recursively until it ends at a Level or Shaft
	 * 
	 *@return	False if not all coordinates are initialized
	 *			|if((square == null) || (coordinate == null))
	 * 			|	then result == false
	 * 			If there is a child dungeon containing the coordinate, a 
	 * 			recursive call will occur
	 * 			|if(hasChildDungeonContainingCoordinate(coordinate))
	 * 			|	then result == ((Dungeon<Square>)getChildDungeonContainingCoordinate(coordinate))
	 *			.canHaveSquareAtCoordinate(square, calculateCoordinateInChildDungeon(coordinate, 
	 *					getChildDungeonContainingCoordinate(coordinate)))
	 *			Returns false otherwise
	 *			|result == false
	 */
	@SuppressWarnings("unchecked")
	@Override
	public boolean canHaveSquareAtCoordinate(E square, Coordinate coordinate){
		if((square == null) || (coordinate == null))
			return false;
		if(hasChildDungeonContainingCoordinate(coordinate))
			return ((Dungeon<Square>)getChildDungeonContainingCoordinate(coordinate))
				.canHaveSquareAtCoordinate(square, calculateCoordinateInChildDungeon(coordinate, 
						getChildDungeonContainingCoordinate(coordinate)));
		return false;
	}

	/**
	 * Calls itself recursively on the child dungeon containing the coordinate
	 * @param square
	 * 			The square to add
	 * @param coordinate 
	 * 			The coordinate at which to add the square
	 * @effect	((Dungeon<Square>)getChildDungeonContainingCoordinate(coordinate))
	 * 				.addSquare(square, calculateCoordinateInChildDungeon(coordinate, 
	 * 				getChildDungeonContainingCoordinate(coordinate)))
	 * @post This dungeon contains the Square
	 * 			|new.contains(square)
	 * @post This dungeon contains the coordinate
	 * 			|new.contains(coordinate)
	 * @throws IllegalArgumentException
	 * 			The coordinate must fall inside a child dungeon
	 * 			|!hasChildDungeonContainingCoordinate(coordinate)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void addSquare(E square, Coordinate coordinate)
			throws IllegalArgumentException {
		modifCount++;
		((Dungeon<Square>)getChildDungeonContainingCoordinate(coordinate)).addSquare(square, calculateCoordinateInChildDungeon(coordinate, getChildDungeonContainingCoordinate(coordinate)));
	}

	/**
	 * Returns whether or not one of the child dungeons of this dungeon
	 * contains that square
	 * 
	 * @param square
	 * 			The square to check for
	 * @return whether or not one of the child dungeons of this dungeon
	 * 			contains that square
	 */
	@SuppressWarnings("unchecked")
	@Override
	public boolean contains(E square) throws IllegalArgumentException {
		for(Dungeon<? extends Square> childDungeon : getChildDungeons().keySet()){
			if(((Dungeon<Square>)childDungeon).contains(square))
				return true;
		}
		return false;
	}

	/** (Returns whether or not one of the child dungeons of this dungeon
	 * contains a square at that coordinate (relative to this composite dungeons
	 * axis')
	 * 
	 * @param coordinate
	 * 			The coordinate to check for
	 * @return whether or not one of the child dungeons of this dungeon
	 * 			contains that a square at that coordinate
	 */
	@Override
	public boolean contains(Coordinate coordinate)
			throws IllegalArgumentException {
		if(dungeonTree.hasChildDungeonContainingCoordinate(coordinate))
			return getChildDungeonContainingCoordinate(coordinate).contains(calculateCoordinateInChildDungeon(coordinate, getChildDungeonContainingCoordinate(coordinate)));
		return false;
	}

	/**
	 * Removes the square at that coordinate (relative in this dungeon)
	 * from the child dungeon it's located in, should that exist
	 * 
	 * @param coordinate
	 * 			The coordinate in to delete the square at
	 * @effect	Removes the square from said child dungeon
	 * 			|getChildDungeonContainingCoordinate(coordinate)
	 * 				.remove(calculateCoordinateInChildDungeon(coordinate, 
	 * 				getChildDungeonContainingCoordinate(coordinate)))
	 * @throws IllegalArgumentException
	 * 			The coordinate must be inside a child coordinate
	 * 			|!hasChildDungeonContainingCoordinate(coordinate)
	 */
	@Override
	public void remove(Coordinate coordinate) throws IllegalArgumentException {
		modifCount++;
		getChildDungeonContainingCoordinate(coordinate).remove(calculateCoordinateInChildDungeon(coordinate, getChildDungeonContainingCoordinate(coordinate)));
	}

	/**
	 * Gets the square at a specific coordinate in this dungeon
	 * 
	 * @param coordinate
	 * 			The coordinate to get the square at
	 * @return 	That square, should it exist
	 * 			|result == getChildDungeonContainingCoordinate(coordinate)
	 * 				.getSquareAtCoordinate(calculateCoordinateInChildDungeon(coordinate, 
	 * 				getChildDungeonContainingCoordinate(coordinate)))
	 * @throws IllegalArgumentException
	 * 			The square at that coordinate must exist
	 * 			|!contains(coordinate)
	 */
	@Override
	public Square getSquareAtCoordinate(Coordinate coordinate)
			throws IllegalArgumentException {
		return getChildDungeonContainingCoordinate(coordinate).getSquareAtCoordinate(calculateCoordinateInChildDungeon(coordinate, getChildDungeonContainingCoordinate(coordinate)));
	}

	/**
	 * Gets the coordinate of a square located in a certain child
	 * dungeon of this one, should it exist, relative to this dungeons
	 * axis'
	 * 
	 * @param square
	 * 			The square for which to get the coordinate
	 * @return
	 * 			Said coordinate
	 * @throws IllegalArgumentException
	 * 			The dungeon must contain said square
	 * 			|!contains(square)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public Coordinate getCoordinateOfSquare(E square)
			throws IllegalArgumentException {
		Dungeon<E> childDungeon = Dungeon.getDungeonContainingSquare(square);
		Coordinate coordinate = childDungeon.getCoordinateOfSquare(square);
		if(!hasAsChildDungeon(childDungeon))
			throw new IllegalArgumentException("The square isn't inside any child of this dungeon");
		while(childDungeon.hasParentDungeon() && !childDungeon.equals(this)){
			coordinate = calculateCoordinateInThisDungeon(coordinate, childDungeon);
			childDungeon = (Dungeon<E>) childDungeon.getParentDungeon();
		}
		return coordinate;
			
	}

	@Override
	public HashSet<E> getEnclosingRoom(E square)
			throws IllegalArgumentException {
		return (HashSet<E>) Dungeon.getDungeonContainingSquare(square).getEnclosingRoom(square);
	}
	
	/**
	 * Checks whether or not a child dungeon of this dungeon can have these new sizes as
	 * its sizes, without a spatial overlap with the other child dungeons occurs
	 * Uses a KDTree to ensure testing in O(log n) time.
	 * 
	 * @param dungeon
	 * 			The dungeon which sizes change
	 * @param newXSize
	 * 			The dungeons new X size
	 * @param newYSize
	 *			The dungeons new Y size
	 * @param newZSize
	 * 			The dungeons new Z size
	 * @return Whether or not that new size is allowed
	 * @throws IllegalArgumentException
	 * 			The dungeon must be initialized
	 * 			|dungeon == null
	 * 			The dungeon must be a child of this one
	 * 			|!hasAsChildDungeon(dungeon)
	 */
	public boolean childDungeonCanHaveNewSize(Dungeon<? extends Square> dungeon, long newXSize, long newYSize, long newZSize) throws IllegalArgumentException{
		if(dungeon == null)
			throw new IllegalArgumentException("Please initialize the dungeon");
		if(!hasAsChildDungeon(dungeon))
			throw new IllegalArgumentException("The dungeon isn't a child of this one");
		return dungeonTree.dungeonCanHaveNewSize(dungeon, childrenDungeons.get(dungeon), newXSize, newYSize, newZSize);
	}
	
	/**
	 * Sets the new dungeons size to its new values.
	 * This is for ensuring internal consistency
	 * 
	 * @param dungeon
	 * 			The dungeon to reset the size for
	 * @throws IllegalArgumentException
	 * 			The dungeon must be initialized
	 * 			|dungeon == null
	 * 			The dungeon must be a child dungeon of this dungeon, or this dungeon itself
	 * 			|!hasAsChildDungeon(dungeon) && !dungeon.equals(this)
	 */
	public void setNewDungeonSize(Dungeon<? extends Square> dungeon) throws IllegalArgumentException{
		if(dungeon == null)
			throw new IllegalArgumentException("Please initialize the dungeon");
		if(!hasAsChildDungeon(dungeon) && !dungeon.equals(this))
			throw new IllegalArgumentException("The dungeon isn't a child of this one");
		modifCount++;
		if(dungeon.equals(this) && dungeonTree != null){
			HashMap<Dungeon<? extends Square>, Coordinate> map = dungeonTree.getAllDungeonsInNodeSubTree();
			dungeonTree = new KDTree(Coordinate.ORIGIN,  new Coordinate(getXSize()-1,getYSize()-1,getZSize()-1));
			for(Dungeon<?> dungeonInMap : map.keySet()){
				dungeonTree.addDungeonToNode(dungeonInMap, map.get(dungeonInMap));
			}
		}
		else if(dungeonTree != null)
			dungeonTree.setNewDungeonSize(dungeon, childrenDungeons.get(dungeon));
	}

	/**
	 * Checks whether the otherSquare is in the same room as firstSquare, which
	 * is expected to fall inside this dungeon.
	 * 
	 * @param firstSquare
	 * 			The square inside this dungeon to check the room for
	 * @param otherSquare
	 * 			The other square to check for if it's in the same room
	 * @return Whether or not those squares are in the same room
	 * 			|((Dungeon<Square>)getChildDungeonContainingCoordinate(
	 * 				getCoordinateOfSquare(firstSquare)))
	 * 				.isSquareInSameRoom(firstSquare, otherSquare)
	 * @throws IllegalArgumentException
	 * 			The firstSquare must be inside this dungeon
	 * 			|!contains(firstSquare)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public boolean isSquareInSameRoom(E firstSquare, E otherSquare)
			throws IllegalArgumentException {
		return ((Dungeon<Square>)getChildDungeonContainingCoordinate(getCoordinateOfSquare(firstSquare))).isSquareInSameRoom(firstSquare, otherSquare);
	}

	@SuppressWarnings("unchecked")
	@Override
	public HashSet<E> getAllReachableSquares(E square)
			throws IllegalArgumentException {
		return (HashSet<E>) ((Dungeon<Square>)getChildDungeonContainingCoordinate(getCoordinateOfSquare(square))).getAllReachableSquares(square);
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public String getRoomDescription(E square) throws IllegalArgumentException {
		return ((Dungeon<Square>)getChildDungeonContainingCoordinate(getCoordinateOfSquare(square))).getRoomDescription(square);
	}
	
	@Override
	public Iterator<E> iterator() {
		return this.new CompositDungeonIterator();
	}
	
	/**
	 * An Iterator that iterates over all squares inside a Dungeon.
	 * Also features the getAllSquaresMatchingConstraint(constraint) method that
	 * returns all squares inside the dungeon matching a certain constraint.
	 * 
	 * @author Tom Jacobs & Ewoud De Moor
	 * @version 1.0
	 */
	public class CompositDungeonIterator implements Iterator<E>{

		private long modificationCount = modifCount;
		
		/* 
		 * READ THIS PLEASE:
		 * 
		 * Choosing to use the built in iterator function of Sets in Java is equivalent
		 * with using reflection to loop through the private 'elementData' array in
		 * HashMap's implementation, but a lot less open for errors, and independent 
		 * of future changes to the private (static) class AbstractMapIterator<K, V>.
		 * 
		 */
		private Iterator<? extends E> it;
		private Iterator<Dungeon<? extends E>> dungeonIt;
		private E currentSquare;
		
		private CompositDungeonIterator() {
			dungeonIt = getAllLevelsAndShafts().keySet().iterator();
			if(dungeonIt.hasNext())
				it = dungeonIt.next().iterator();
			else
				it = new HashSet<E>().iterator();
		}
		@Override
		public boolean hasNext() {
			concurrentModificationCheck();
			if(!it.hasNext() && dungeonIt.hasNext()){
				while(!it.hasNext() && dungeonIt.hasNext())
					it = dungeonIt.next().iterator();
			}
			return it.hasNext();
		}

		private void concurrentModificationCheck() {
			if(modifCount != modificationCount)
				throw new ConcurrentModificationException("Please only use the iterator to modify this dungeon");
			
		}
		@Override
		public E next() throws NoSuchElementException{
			if(!hasNext())
				throw new NoSuchElementException("No more elements found");
			currentSquare = it.next();
			return currentSquare;
		}

		@Override
		public void remove() {
			concurrentModificationCheck();
			if(currentSquare == null)
				throw new IllegalStateException("There is no element to remove");
			it.remove();
			currentSquare = null;
		}
		

		/**
		 * Returns all squares in this dungeon that match the constraint
		 * 
		 * @param constraint
		 * 			The constraint to match
		 * @return 	All squares matching that constraint
		 */
		public HashSet<E> getAllSquaresMatchingConstraint(Constraint constraint){
			HashSet<E> allSquaresMatchingConstraint = new HashSet<E>();
			for(Dungeon<? extends E> dungeon : getAllLevelsAndShafts().keySet()){
				Iterator<? extends E> it = dungeon.iterator();
				allSquaresMatchingConstraint.addAll(((dungeon.Dungeon<? extends E>.DungeonIterator) it).getAllSquaresMatchingConstraint(constraint));
			}
			return allSquaresMatchingConstraint;
		}
	}
	
}
